Stream·Generic·Lambda로 중복 코드 제거
NOTE
칵테일 맛/재료/추천 맵핑 리스트를 응답 DTO(Map)로 변환하는 반복 코드를, 제네릭 메서드 + 람다로 통합한 리팩토링 기록.
Before — 유사 구조가 반복되는 코드
맛(Taste), 재료(Ingredient), 추천(Recommend) 3개 매핑 리스트를 각각 Map으로 변환하는 메서드가 거의 동일한 구조로 3번 반복되어 있었다.
private List<Map<String,Object>> getResTastes(List<MappingTaste> tastes){
Map<String, Object> tasteMap = new HashMap<>();
List<Map<String, Object>> tastesMaps = new ArrayList<>();
for (MappingTaste taste : tastes) {
tasteMap.put("category", taste.getTasteCategory());
tasteMap.put("tasteDetail", taste.getTasteDetail());
tastesMaps.add(tasteMap);
}
return tastesMaps;
}
private List<Map<String, Object>> getResIngredients(List<MappingIngredient> ingredients){
List<Map<String,Object>> ingredientMaps = new ArrayList<>();
Map<String, Object> ingredientMap = new HashMap<>();
for (MappingIngredient ingredient : ingredients) {
ingredientMap.put("ingredient", ingredient.getIngredient());
ingredientMap.put("unit", ingredient.getUnit());
ingredientMap.put("quantity", ingredient.getQuantity().toString());
ingredientMaps.add(ingredientMap);
}
return ingredientMaps;
}
private List<Map<String ,Object>> getResRecommends(List<MappingRecommend> recommends){
List<Map<String, Object>> recommendMaps = new ArrayList<>();
Map<String, Object> recommendMap = new HashMap<>();
for (MappingRecommend recommend : recommends) {
recommendMap.put("mood", recommend.getMood());
recommendMap.put("situation", recommend.getSituation());
recommendMap.put("season", recommend.getSeason());
recommendMaps.add(recommendMap);
}
return recommendMaps;
}세 메서드 모두 “리스트를 순회하며 각 항목을 Map으로 변환해 리스트에 담는다”는 동일한 골격을 가지고 있어, 공통 부분을 제네릭 메서드로 추출하고 항목별 매핑 로직만 람다로 주입하는 방향으로 리팩토링했다.
After — 제네릭 + 람다 적용
private <T> List<Map<String, Object>> convertToMapList(List<T> list, Function<T, Map<String,Object>> mapper){
return list.stream()
.map(mapper)
.collect(Collectors.toList());
}
private List<Map<String, Object>> getResTastes(List<MappingTaste> tastes){
return convertToMapList(tastes, taste -> {
Map<String, Object> map = new HashMap<>();
map.put("category", taste.getTasteCategory());
map.put("tasteDetail", taste.getTasteDetail());
return map;
});
}
private List<Map<String, Object>> getResIngredients(List<MappingIngredient> ingredients){
return convertToMapList(ingredients, ingredient -> {
Map<String, Object> map = new HashMap<>();
map.put("ingredient", ingredient.getIngredient());
map.put("unit", ingredient.getUnit());
map.put("quantity", ingredient.getQuantity().toString());
return map;
});
}
private List<Map<String, Object>> getResRecommends(List<MappingRecommend> recommends){
return convertToMapList(recommends, recommend -> {
Map<String, Object> map = new HashMap<>();
map.put("mood", recommend.getMood());
map.put("situation", recommend.getSituation());
map.put("season", recommend.getSeason());
return map;
});
}정리
- Stream: 리스트 순회 + 변환(
map) + 수집(collect)을 선언적으로 표현해 for문 없이 반복 로직을 축약 - Generic (
<T>):MappingTaste,MappingIngredient,MappingRecommend처럼 타입이 다른 입력에도 하나의 메서드(convertToMapList)를 재사용 가능하게 함 - Lambda (
Function<T, Map<String,Object>>): 타입별로 달라지는 “필드를 Map에 담는 방법”만 호출부에서 주입 — 공통 골격(순회+수집)과 가변 로직(필드 매핑)을 분리
이 리팩토링 이전에는 Provider(Kakao/Naver/Google/Apple)별로 제각각이던 소셜 로그인 로직도 유사한 문제를 안고 있었는데, 그쪽은 Strategy + Factory 패턴으로 해결했다. 자세한 내용은 [[구현] OAuth 소셜 로그인 설계 (code 기반 인증, Strategy+Factory)] 문서 참고.
관련 문서
- OAuth 소셜 로그인 설계 (code 기반 인증, Strategy+Factory) — 유사한 Provider별 반복 로직 문제를 Strategy+Factory 패턴으로 해결한 문서
- Onz 프로젝트 소개 및 기획 — 이 리팩토링이 이루어진 Onz 프로젝트의 전체 기획